class Operation < ActiveRecord::Base
  has_many :amount_changes, :dependent => :destroy
  has_and_belongs_to_many :tags, :autosave => true
  has_and_belongs_to_many :scheduled_records

  belongs_to :user
  validates_presence_of :user

  accepts_nested_attributes_for :amount_changes, :reject_if => lambda { |a| a["delta"].blank? }, :allow_destroy => true

  before_create :increasing_tags_counter
  before_destroy :decreasing_tags_counter

  def operation_result
    AmountChange.where(:operation_id => self.id).group(:amount_id).sum(:delta)
  end

  def operation_result_
    res = {}
    operation_result.each do |k,v|
      amount = Amount.find(k)
      res["@#{amount.wallet.name}"] = {amount.currency_code => v}
    end
    
    return res
  end

  def external_format
    # TODO witch wallet?
    res = self.operation_result_.merge({
      :id => self.id,
      :comment => self.description,
      :date => self.record_date,
      :author => self.user_id,
    })
    res[:state] = self.state if self.state != 1
    res
  end

  #
  # operation_params = {
  #   comment: Text
  #   date: int
  #   state: -1,0,1 # deleted, not-approved, approved
  #   wallet: <Wallet Name>
  #   value: { # TODO here shouldn't be hash, should be plain params
  #     USD: "1000",
  #     BYR: "10000",
  #   }
  # }
  #
  # operation_params = {
  #   comment: Text
  #   date: int
  #   author: ??? # TODO it should be handled
  #   "@<Wallet Name>": {USD: "1000"}
  # }
  #
  def self.from_options(user, operation_params)
    user ||= User.find(operation_params["user_id"] || operation_params["author"])

    operation = Operation.new(
        :user_id => user.id,
        :description => operation_params["comment"],
        :record_date => operation_params["date"] || Time.now.gmtime.to_i,
        :state => operation_params["state"] || 1 # approved
    )

    res = {}
    operation_params.each do |k,v|
      if k[0] == '@'
        res["wallet"] = k[1..-1]
        res["value"] = v
      end
    end
    operation_params.merge!(res)

    wallet = user.wallets.find_by_name(operation_params["wallet"]) || user.default_wallet

    amounts = wallet.amounts
    operation_params["value"].each do |currency_code, value|

      # TODO create in wallet hash of amounts based on amounts collection
      walletAmount = amounts.to_a.find { |a| a.currency_code == currency_code }
      unless walletAmount
        walletAmount = wallet.amounts.build(:currency_code => currency_code)
        walletAmount.save!
      end

      if value && (value.kind_of?(String) && !value.empty? || value.kind_of?(Fixnum))
        operation.amount_changes.build(:delta => value, :amount => walletAmount, :operation => operation)
      end
    end

    # todo tags?

    operation
  end

  #
  # operation_options = {
  #   value: Float
  #   currency: String | nil
  #   wallet: String | nil
  #   date: int
  #   comment: {
  #     text: String
  #     tags: [String]
  #     wallets: [String]
  #     date: String
  #   }
  # }
  #
  def self.from_tweet_options user, operation_options
    op = user.operations.build(
        :description    => operation_options[:comment][:text],
        :record_date => operation_options[:date] || Time.now.gmtime.to_i,
        :state => operation_options["state"] || 1 # approved
    )

    op.create_amount_change(user, operation_options)
    op.create_tags(user.wallet_by_name(operation_options[:wallet]), operation_options[:comment][:tags])

    return op
  end

  def create_amount_change(user, options)
    wallet = user.wallet_by_name(options[:wallet])

    currencyCode = options[:currency]
    if currencyCode
      currency = Currency.find_by_code(currencyCode)
      errors.add("currency", "No such currency (#{currencyCode})")
    end
    currencyCode = user.default_currency_code if !currencyCode || !currency

    amount = wallet.amount_by_currency_code(currencyCode)
    unless amount
      amount = wallet.amounts.create(:currency_code => currencyCode)
    end

    self.amount_changes.build(:delta => options[:value], :amount => amount, :operation => self)
  end

  def create_tags(wallet, tags)
    tags.each { |t| self.tags << Tag.find_or_initialize_by_wallet_id_and_name(:wallet_id => wallet.id, :name => t) }
  end

  private
    def decreasing_tags_counter
      self.tags.each do |t|
        t.operations_count -= 1
        t.save!
      end
    end

    def increasing_tags_counter
      tags.each {|t| t.operations_count += 1 and t.save! }
    end
end
